/**
 *
 */
package yuan.plugins.banBreak;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;

import com.google.common.collect.Maps;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import lombok.val;
import lombok.experimental.FieldDefaults;

/**
 * 配置数据
 *
 * @author yuanlu
 *
 */
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@ToString
public class Conf {
	/**
	 * 玩家对某一方块破坏的数据
	 *
	 * @author yuanlu
	 *
	 */
	private static final class PlayerConf {
		/**
		 * 延时处理
		 *
		 * @author yuanlu
		 *
		 */
		private final class Call implements Delayed, Runnable {
			/** 期望时间 */
			private final long time = nextShow;

			@Override
			public int compareTo(Delayed o) {
				return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
			}

			@Override
			public long getDelay(TimeUnit unit) {
				return unit.convert(time - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
			}

			@Override
			public void run() {
				if (time != nextShow) return;
				breakOnce = false;
			}

		}

		/** 下一次唤醒时间 */
		long	nextShow	= Long.MIN_VALUE;

		/** 是否已经破坏过一次 */
		boolean	breakOnce	= false;

		/** @return call实例 */
		public Call call() {
			return new Call();
		}

	}

	/** 所有配置 */
	private static final ArrayList<Conf>							CONFS			= new ArrayList<>();
	/** 默认配置 */
	private static final Conf										DEFAULT			= new Conf();

	/** 种类到配置文件的映射 */
	private static final EnumMap<Material, Conf>					TYPES_MAP		= new EnumMap<>(Material.class);

	/** 玩家缓存 */
	private static final Map<UUID, HashMap<Material, PlayerConf>>	PLAYER_CACHE	= new ConcurrentHashMap<>();

	/** 守护清理 */
	private static final DelayQueue<PlayerConf.Call>				MAINTAIN		= new DelayQueue<>();
	static {
		new Thread(Main.getMain().getName() + ": breakWait-" + Main.getMain().hashCode()) {
			@Override
			public void run() {
				while (true) {
					try {
						val runnable = MAINTAIN.take();
						if (!Main.getMain().isEnabled()) return;
						runnable.run();
					} catch (Throwable e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

	/**
	 * 清理缓存
	 *
	 * @param uniqueId 玩家UUID
	 */
	public static void clear(UUID uniqueId) {
		PLAYER_CACHE.remove(uniqueId);
	}

	/**
	 * 创建配置文件
	 *
	 * @param config 文件配置节点
	 * @param def    默认配置文件
	 * @return 配置文件
	 */
	@NonNull
	private static Conf create(ConfigurationSection config, @NonNull Conf def) {
		boolean			protectBreak	= config.getBoolean("protect-break", def.protectBreak);
		boolean			banBreak		= config.getBoolean("ban-break", def.banBreak);
		String			msg				= t(config.getString("msg", def.msg));
		long			showAgain		= config.getLong("show-again", def.showAgain);
		List<String>	list_str		= config.isList("list") ? config.getStringList("list") : null;

		Set<Material>	list			= list_str == null ? def.list : EnumSet.noneOf(Material.class);
		if (list_str != null) list_str.stream() //
				.map(Material::matchMaterial) //
				.filter(Objects::nonNull)//
				.filter(Material::isBlock)//
				.forEach(list::add)//
		;

		return new Conf(protectBreak, banBreak, msg, showAgain, list);
	}

	/**
	 * 获取配置文件
	 *
	 * @param material 材料
	 * @return 配置文件
	 */
	public static Conf get(@NonNull Material material) {
		return TYPES_MAP.get(material);
	}

	/**
	 * 初始化
	 */
	public synchronized static void init() {

		CONFS.clear();
		TYPES_MAP.clear();

		val	config		= Main.getMain().getConfig();
		val	def			= create(config.getConfigurationSection("def-setting"), DEFAULT);
		val	settings	= config.getConfigurationSection("settings");

		if (settings != null) {
			for (val key : settings.getKeys(false)) {

				val conf = create(settings.getConfigurationSection(key), def);
				CONFS.add(conf);

			}
		}

		for (val conf : CONFS) {
			conf.list.forEach(type -> TYPES_MAP.put(type, conf));
		}

	}

	/**
	 * 翻译字符串<br>
	 * 将文字中的彩色字符串进行翻译
	 *
	 * @param s 字符串
	 * @return 翻译后的字符串
	 */
	public static final String t(String s) {
		return ChatColor.translateAlternateColorCodes('&', s);
	}

	/** 保护模式 */
	boolean			protectBreak	= true;
	/** 禁止模式 */
	boolean			banBreak		= false;
	/** 显示消息 */
	String			msg				= ChatColor.translateAlternateColorCodes('&', "&c此方块破坏后不掉落.");

	/** 再次显示时长 */
	long			showAgain		= 5000;

	/** 方块列表 */
	Set<Material>	list			= Collections.emptySet();

	/**
	 * 是否取消破坏
	 *
	 * @param player   玩家
	 * @param material 方块材质
	 * @return 是否取消
	 */
	public boolean cancelBreak(Player player, Material material) {

		val	playerConf	= PLAYER_CACHE.computeIfAbsent(player.getUniqueId(), u -> Maps.newHashMapWithExpectedSize(TYPES_MAP.size()));
		val	blockConf	= playerConf.computeIfAbsent(material, m -> new PlayerConf());
		val	time		= System.currentTimeMillis();

		try {
			if (blockConf.nextShow <= time) player.sendMessage(parseVar(msg, '<', '>', Collections.singletonMap("block", material)));

			blockConf.nextShow = time + showAgain;
			val breaked = blockConf.breakOnce;

			blockConf.breakOnce = true;
			return banBreak || protectBreak && !breaked;
		} finally {
			MAINTAIN.add(blockConf.call());
		}
	}

	/**
	 * 解析字符串含义<br>
	 * 使用指定字符包围的字符串将作为变量名解析为对应数据<br>
	 * 例子:
	 *
	 * <pre>
	 * msg: 你好{@code <player>}, 我是{@code <who>}, 现在已经{@code <time>}点了！
	 * start: {@code '<'}
	 * end: {@code '>'}
	 * vars:{
	 *     player: yuanlu,
	 *     who: Administrators
	 *     time: 7
	 * }
	 *
	 * return: 你好yuanlu, 我是Administrators, 现在已经7点了！
	 * </pre>
	 *
	 * @param msg   原消息
	 * @param start 变量开始字符
	 * @param end   变量结束字符
	 * @param vars  变量集合
	 * @return 解析后字符串
	 */
	public static String parseVar(final String msg, char start, char end, Map<String, Object> vars) {
		int startIndex;
		if (msg == null || (startIndex = msg.indexOf(start)) < 0) return msg;
		try {
			StringBuilder sb = new StringBuilder();
			if (startIndex > 0) sb.append(msg, 0, startIndex);
			for (int i = startIndex; i < msg.length(); i++) {
				char c = msg.charAt(i);
	
				if (c == start) {// 找到开始标记
					int e = msg.indexOf(end, i + 1);// 寻找结束标记
					if (e < 0) {// 没有结束标记
						sb.append(msg.substring(i));
						return sb.toString();// 直接返回
					}
	
					String var_str = msg.substring(i + 1, e);// 变量名
	
					if (var_str.isEmpty()) {// 变量名为空
						if (start == end) sb.append(start);// 转意
						else sb.append(start).append(end);// 不是变量
					} else {
	
						if (vars.containsKey(var_str)) {// 是变量
							Object var = vars.get(var_str);
							sb.append(var);
						} else {// 不是变量
							sb.append(start).append(var_str).append(end);
						}
	
					}
					i = e;// 跳过变量部分
				} else {
					sb.append(c);// 普通字符
				}
			}
			return sb.toString();
		} catch (Throwable e) {
			e.printStackTrace();
			return msg;
		}
	}
}
